/* Copyright (c) CERN
 * * Artistic License 2.0
 * */

import java.util.*;
import java.io.*;
import java.lang.*;

/**
 * The Monte Carlo class analyzes a memory accesses trace, fits the values to a distribution,
 * and generates new values according to the estimated distribution.
 * The result is displayed in a file and values are sorted to be plotted against the input values,
 * which gives an visual grasp of the quality of the fit.
 *
 * Usage: java MC inputfile [outputfile] [minvalue] [iterations]
 *
 * if minvalue is given and not iterations, it returns the trunkated estimation
 * if iteration is non zero, it returns the biased estimation
 * if iteration is zero or minvalue is not given, it returns the whole estimation
 *
 * @author Xavier Grehant
 */
class MC {

/**
 * This method skips the comments and returns the first line which is not a comment.
 * @return the first line which is not a comment.
 * @param rinput the input lines as a BufferedReader.
 */
   static String readValue(BufferedReader rinput) throws IOException {
      String line;
      while ((line = rinput.readLine()) != null && line.startsWith("#"));
      return line;
   }

/**
 * This method generates a list of random numbers according to the distribution
 * passed as an argument.
 * @return a list of random numbers according to the specified distribution.
 * @param distro the specified distribution
 * @param length the length of the output list.
 */
   static double[] pourDistro(Distro distro, int length) {
      double[] list = new double[length];
      for (int i = 0; i < length; i++) {
         list[i] = distro.nextDouble();
      }
      return list;
   }

/**
 * This method generates a list of random numbers according to the discrete distribution
 * passed as an argument where the zero values are replaced by random numbers according
 * to the continuous distribution passed as an argument.
 * @return a list of random numbers according to the specified distribution.
 * @param discrete the discrete distribution.
 * @param distro the continuous distribution.
 * @param length the length of the output list.
 */
   static double[] pour(Discrete discrete, Distro distro, int length) {
      double[] list = new double[length];
      Arrays.fill(list, 0);
      for (int i = 0; i < length; i++) {
         list[i] = discrete.nextInt();
         if (list[i] == 0) list[i] = distro.nextDouble();
      //zero values in a discrete distribution correspond to continuous values in the input.
      }
      return list;
   }

/**
 * This method prints the Monte Carlo generated variable in a file for display with gnuplot.
 * The second column in the file is the cdf deviation of the model w/r to the actual distro.
 * @return
 * @param model the model distribution generated samples.
 * @param array the actual samples.
 * @param filename the name of the file to write.
 */
   static void write(Distro model, int[] array, String filename) throws IOException {

      double[] ground = pourDistro(model, array.length);
      //double[] list = pour(discrete, distro, size);
      Arrays.sort(ground);

      Arrays.sort(array);
      int max = array[array.length-1];

      Actual actual = new Actual(array);
      double step = (double) max / (double) array.length;
      double value = 0;
      double[] err = new double[array.length];
      double cumErr = 0.;
      for (int i = 0; i < array.length; i++) {
         value += step;
         double av = actual.cdf(value);
         double mv = model.cdf(value);
         double denom = Math.abs(av) + Math.abs(mv);
         if (denom != 0) err[i] = (av - mv) / denom;
         else err[i] = 0;
         cumErr += Math.abs(err[i]);
      }

      FileWriter outfile = new FileWriter(new File(filename));
      outfile.write("# Mean error : " + cumErr / (double) array.length + "\n");
      for (int i = array.length - 1; i >= 0; i--) {
         outfile.write(ground[i] + " " + err[i] + "\n");
         // Caution: column values are not distributed on the same scale.
         // Caution: big values first.
      }
      outfile.close();
   }

/**
 * Main method.
 * This class handles the files, vectors and lists, estimates the discrete and continuous
 * parts according to the user input and writes the result.
 * @return
 * @param args user input. See 'Usage' in the class documentation.
 */
    public static void main(String args[]) {
        if (args.length > 3) {
            Vector fromFile = new Vector();
            try {
                // Open file
                FileInputStream fstream = new FileInputStream(args[0]);
                BufferedReader in = new BufferedReader(new InputStreamReader(fstream));

                // Create initial vector with real data.
                String newValue;
                while ((newValue = readValue(in)) != null) {
                    Integer intValue = new Integer(newValue);
                    fromFile.add(intValue);
                }
                // Close file
                in.close();

            } catch (Exception e) {
                e.printStackTrace();
            }

                int[] fromVector = new int[(int) fromFile.size()];
                for (int index = 0; index < fromFile.size(); index++) {
                   fromVector[index] = ((Integer) fromFile.elementAt(index)).intValue();
                }
                int size = fromVector.length;
                System.out.println("Size : " + size);

                // Estimate discrete variable
                // Discrete discrete = new Discrete(fromVector, 10,
                //                               .02f, 10);
                // fromVector = discrete.cleanOut(fromVector);

                // Estimate Distro based on our array
                Distro distro;
                int minValue = 0, iterations = 0;

                if (args.length > 2 && args[2].contains("Cosine")) {
                   if (args.length < 4) {
                      System.out.println("Invalid parameters");
                      return;
                   }
                   int paramn = (new Integer(args[3])).intValue();
                   distro = new Cosine(fromVector, paramn);
                } else if (args.length > 2 && args[2].contains("Histo")) {
                   if (args.length < 4) {
                      System.out.println("Invalid parameters");
                      return;
                   }
                   int paramn = (new Integer(args[3])).intValue();
                   distro = new Histo(fromVector, paramn);
                } else if (args.length > 2 && args[2].contains("Opso")) {
                   if (args.length < 4) {
                      System.out.println("Invalid parameters");
                      return;
                   }
                   int paramn = (new Integer(args[3])).intValue();
                   distro = new Opso(fromVector, paramn);
                } else {

                   if (args.length > 3) minValue = (new Integer(args[3])).intValue();
                   if (args.length > 4) iterations = (new Integer(args[4])).intValue();

                if (args[2].contains("GPD-biased")) distro = GPD.biased(fromVector, minValue, iterations); 
                else if (args[2].contains("GPD")) distro = new GPD(fromVector);
                else if (args[2].contains("Gamma-biased")) distro = Gamma.biased(fromVector, minValue, iterations); 
                else if (args[2].contains("Gamma")) distro = new Gamma(fromVector);
                //else if (args[2].contains("HN-biased")) distro = HalfNormal.biased(fromVector, minValue, iterations); 
                //else if (args[2].contains("HN")) distro = new HalfNormal(fromVector);
                else if (args[2].contains("Uniform-biased")) distro = Uniform.biased(fromVector, minValue, iterations); 
                else if (args[2].contains("Uniform")) distro = new Uniform(fromVector);

                else { System.out.println("Invalid parameter: " + args[2]); return; }

                }

		Distro actual = new Actual(fromVector);

                 // Monte Carlo simulation
            try {
               String filename = args[1];
               write(distro, fromVector, filename);
            } catch (IOException e) {
               e.printStackTrace();
            }
        }
        else
            System.out.println("Invalid parameters");
    }
}
